/**
 * 公共方法类
 * */
import dayjs, { Dayjs } from "dayjs";
import _ from "lodash";

export class UtilTool {
  // 集成loadash
  static lodash: _.LoDashStatic = _;

  // 集成dayjs
  static dayjs: typeof dayjs = dayjs;

  /**
   * 生成树结构
   * @param {object} data - 原数组
   * @param {[string,string]} relation - 父子关系字段
   * @param {string="children"} childField - 子节点字段名
   * @param {boolean=false} moreSon - 允许多个不同父存在相同子
   * 是否相同子出现在不同父中，如果为true，则parentCode值应为逗号拼接的多个父dictCode）
   * @return {object} tree
   * */
  static makeTree(
    data: any[],
    relation: [string, string] = ["dictCode", "parentCode"],
    childField: string = "children",
    moreSon: boolean = false
  ) {
    const [sonField, parentField] = relation;

    // 删除 所有 children,以防止多次调用
    data.forEach(item => {
      delete item[childField];
    });

    // 将数据存储为 以 dictCode 为 KEY 的 map 索引数据列
    const map: Record<string, any> = {};
    data.forEach(item => {
      map[item[sonField]] = item;
    });

    const val: any = [];

    const setVal = (parent: any, item: any) => {
      // 如果找到索引，那么说明此项不在顶级当中,那么需要把此项添加到，他对应的父级中
      if (parent) {
        parent[childField] || (parent[childField] = []);
        parent[childField].push(item);
      } else {
        //如果没有在map中找到对应的索引dictCode,那么直接把 当前的item添加到 val结果集中，作为顶级
        val.push(item);
      }
    };

    data.forEach(item => {
      if (moreSon) {
        // 以当前遍历项的parentCode,去map对象中找到索引的dictCode
        for (const parentCode of item[parentField].split(",")) {
          const parent = map[parentCode];
          setVal(parent, item);
        }
      } else {
        // 以当前遍历项的parentCode,去map对象中找到索引的dictCode
        const parent = map[item[parentField]];
        setVal(parent, item);
      }
    });

    return val;
  }

  /**
   * 返回扁平化数组
   * @param {any[]} data - 原数组
   * @param {string} childField - 下一级数据的字段名
   */
  static flatArr(data: any[], childField: string = "") {
    const result: any[] = [];
    const deepFn = (arr: any[]) => {
      arr.forEach(e => {
        result.push(e);
        if (e[childField] && e[childField].length > 0) deepFn(e[childField]);
      });
      return result;
    };
    return deepFn(data);
  }

  /**
   * 将扁平化数组进行归纳整理
   * @param {any[]} data - 原数组
   * @param {string} key - 根据指定key进行分类归纳
   * @return {any[][]} 返回二维数组
   * */
  static fixFlatArr(data: any[], key: string) {
    const result: any[] = [];
    data.forEach(item => {
      const value = item[key];
      if (!result.find(d => d.find((e: any) => e[key] === value))) {
        result.push(data.filter(e => e[key] === value));
      }
    });
    return result;
  }

  /**
   * @title query转json
   * @param {string} key - json.key
   * @param {string} url - /aaa?a=1&b=2&c=3 如果不传默认返回空对象
   * @param {string[]} joinSymbol - 连接符[s1,s2,s3]
   * s1：path与query的连接符，默认?
   * s2：多个query的连接符，默认&
   * s3：query的kv连接符，默认=
   * @return {json}
   */
  static queryToJson(key: string = "", url: string = "", joinSymbol = ["?", "&", "="]) {
    const [s1, s2, s3] = joinSymbol;
    if (!url) return {};
    const queryArr = url?.split(s1)[1]?.split(s2) || [];
    if (!queryArr.length) return key ? "" : {};
    const json = queryArr.reduce((acc: Record<string, any>, curr) => {
      const [key, value] = curr.split(s3);
      acc[key] = value;
      return acc;
    }, {});
    if (!key) return json;
    return json[key] ? json[key] : "";
  }

  /**
   * @title json转query
   * @param {object} json
   * @param {boolean} includeEmpty - 是否包含空字符串
   * @param {string[]} excludeKeys - 排除的字段
   * @return {string}
   */
  static jsonToQuery(
    json: Record<string, any> = {},
    includeEmpty = false,
    excludeKeys: string[] = []
  ) {
    const queryArr = [];
    for (const key in json) {
      if (!excludeKeys.includes(key)) {
        if (!includeEmpty) {
          if (json[key]) queryArr.push(`${key}=${json[key]}`);
        } else {
          queryArr.push(`${key}=${json[key]}`);
        }
      }
    }
    return queryArr.join("&");
  }

  /**
   * 数字转千分位
   * @param {number} num - 数值
   * @return {string}
   * */
  static numberToLocal = (num: number): string => {
    return !isNaN(Number(num)) ? num.toLocaleString() : "0";
  };

  /**
   * 获取树节点指定级别数据集合
   * @param {any[]} treeData - 树形数据
   * @param {number} level - 指定级别
   * @param {string} childField - 子节点字段名
   * @return {any[]}
   * */
  static getDatasByTreeLevel = (
    treeData: any[] = [],
    level: number = 0,
    childField: string = "children"
  ): any[] => {
    const result: any[] = [];
    // 递归获取指定级别数据
    // dataSource：需要递归处理的数据
    // currentLevel：从哪个级别开始
    const deepGetMenus = (dataSource: any[], currentLevel: number) => {
      if (currentLevel === level) {
        result.push(...dataSource);
        return;
      }
      dataSource.forEach(data => {
        deepGetMenus(data[childField] || [], currentLevel + 1);
      });
    };
    deepGetMenus(treeData, 0);
    return result;
  };

  /**
   * 根据指定时间，返回距离今天还有多少天
   * @param {string|Dayjs} date - 时间格式字符串
   * @return {number} - 返回天数
   * */
  static getDiffDayByDate = (date: string | Dayjs): number => {
    return dayjs(date).diff(dayjs().format("YYYY-MM-DD"), "day");
  };

  /**
   * 递归获取匹配的值
   * @param {any[]} dataSource - 树形数据源
   * @param {string} value - 匹配的值(是fieldNames.key字段的值)
   * @param {Record<"key" | "children", string>} fieldNames - 树形数据源字段名配置
   * @return {any | null} - 匹配的值
   * */
  static getDataDeepFn(
    dataSource: any[],
    value: string,
    fieldNames?: Record<"key" | "children", string>
  ): any {
    fieldNames = _.merge(
      {},
      {
        key: "id",
        children: "children"
      },
      fieldNames
    );
    for (const data of dataSource) {
      // 检查当前项的key是否匹配
      if (data[fieldNames.key] === value) {
        // 如果匹配，返回该项
        return data;
      }
      // 如果当前项有children属性（即它还有子节点），递归查找
      if (data[fieldNames.children] && data[fieldNames.children].length) {
        const found = UtilTool.getDataDeepFn(data[fieldNames.children], value, fieldNames);
        // 如果在子节点中找到了匹配项，直接返回
        if (found) return found;
      }
    }
    // 如果遍历完都没找到，返回null或其他表示未找到的值
    return null;
  }

  /**
   * @title 将一个数组中具有相同值的某一个字段所在的对象归纳到一组
   * @param {any[]} data 原数组
   * @param {string="id"} key 根据数组对象中的哪个字段进行合并
   * @returns any[][]
   * @example [{id:1,a:1},{id:1,a:2},{id:2,a:1},{id:2,a:2}]
   *  转为
   *  [
   *    [ { id: 1, a: 1 }, { id: 1, a: 2 } ],
   *    [ { id: 2, a: 1 }, { id: 2, a: 2 } ]
   *  ]
   */
  static arrToGroupByField(data = [], key = "id"): any[][] {
    const arrFields = Array.from(new Set(data.map(e => e[key])));
    return arrFields.map(v => data.filter(e => e[key] == v));
  }

  /**
   * 补位函数，小于10前面统一补个0
   * @param {string|number} count - 数值
   * @return {string|number} - 小于10补零返回，否则原样返回
   * */
  static repairZero = (count: string | number): string => {
    return Number(count) > 9 ? String(count) : "0" + count;
  };

  /**
   * 根据byte计算文件大小
   * @param {number} fileSize - 文件大小，单位byte
   * @param {"KB" | "MB" | "GB"} targetUnit - 目标单位
   * */
  static getFileSize(fileSize: number, targetUnit?: FileSizeUnit) {
    const kb = fileSize / 1024;
    const mb = kb / 1024;
    const gb = mb / 1024;

    const getSize = (size: number, unit: FileSizeUnit) => `${Math.ceil(size)}${unit}`;

    const toMap = { KB: kb, MB: mb, GB: gb };
    // 指定返回单位
    if (targetUnit && toMap[targetUnit]) return getSize(toMap[targetUnit], targetUnit);
    // 自动返回单位
    if (gb > 1) return getSize(gb, "GB");
    if (mb > 1) return getSize(mb, "MB");
    return getSize(kb, "KB");
  }

  /**
   * 根据链式key的字符串从对象中取值
   * @param {object} obj - json对象
   * @param {string} key - 链式key
   * */
  static getObjValueByChain(obj: object, key: string) {
    const keys = key.split(".").filter(e => !!e);
    let result = JSON.parse(JSON.stringify(obj));
    for (const key of keys) {
      result = result[key];
    }
    return result;
  }

  /**
   * 对象数组去重，根据指定字段去重对象数组
   * @param {{[key:string]:any}[]} dataSource - 原数组
   * @param {string} key - 字段名
   * @returns {any[]} 如果指定字段存在多次，去重后只返回第一次出现的结果
   * @example ReRepeat([{id:1,name:"张三"},{id:2,name:"李四"},{id:1,name:"王五"}],"id") => [{id:1,name:"张三"},{id:2,name:"李四"}]
   */
  static objectArrRepeat(dataSource: { [key: string]: any }[], key: string): any[] {
    const map = new Map();
    return dataSource.filter(item => !map.has(item[key]) && map.set(item[key], item));
  }

  /**
   * @description: 获取任意长度的随机密码，至少包含一个大写字母、一个小写字母，一个数字、一个特殊符号
   * @param {number} len 随机字符串的长度
   * @return {string}
   * */
  static generatePassword(len: number = 8): string {
    // 参数校验：确保长度不小于4
    len = Math.max(4, Math.floor(len));

    // 定义字符集
    const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const lowercase = "abcdefghijklmnopqrstuvwxyz";
    const numbers = "0123456789";
    const symbols = "!@#$%^&*()_+-=[]{}<>?";

    // 从字符集中随机选取一个字符
    const getRandomChar = (charSet: string) => {
      return charSet[Math.floor(Math.random() * charSet.length)];
    };

    // Fisher-Yates洗牌算法打乱数组
    const shuffleArray = (array: string[]) => {
      for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
      }
    };

    // 确保每种类型至少有一个字符
    const mandatoryChars = [
      getRandomChar(uppercase),
      getRandomChar(lowercase),
      getRandomChar(numbers),
      getRandomChar(symbols)
    ];

    // 生成剩余随机字符
    const allChars = uppercase + lowercase + numbers + symbols;
    const remainingChars = Array.from({ length: len - 4 }, () => getRandomChar(allChars));

    // 合并并打乱顺序
    const passwordChars = [...mandatoryChars, ...remainingChars];
    shuffleArray(passwordChars);

    return passwordChars.join("");
  }

  /**
   * 复制内容
   * @param {string[]} contentArray - 需要复制的内容,数组项是每一行的内容
   * */
  static copyToClip(contentArray: string[]) {
    const contents = contentArray.join("\n");
    const textarea = document.createElement("textarea");
    textarea.value = contents;
    document.body.appendChild(textarea);
    textarea.select();
    if (document.execCommand("copy")) {
      document.execCommand("copy");
    }
    document.body.removeChild(textarea);
  }

  /**
   * 选择文件
   * @param {string} accept - 文件类型
   * @param {boolean} multiple - 是否多选
   * @return {Promise<File[]>} multiple为false，返回的数组只有一个子项，为true则返回多个子项
   * */
  static getFile(accept:string="",multiple=false):Promise<File[]> {
    const fileSelectDom = document.createElement("input");
    fileSelectDom.type = "file";
    fileSelectDom.accept = accept;
    if(multiple) fileSelectDom.multiple = true;
    fileSelectDom.click();
    return new Promise((resolve) => {
      fileSelectDom.onchange = function () {
        let files:File[] = [];
        if(fileSelectDom.files?.length) {
          files =  multiple ? Array.from(fileSelectDom.files as any) : [fileSelectDom.files[0]];
        }
        resolve(files);
        fileSelectDom.onchange = undefined as any;
      }
    })
  }
}

// 文件大小单位
export declare type FileSizeUnit = "KB" | "MB" | "GB";
